﻿#region Header

/*
Behavioral Rating of Dancing Human Crowds based on Motion Patterns
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;

#endregion

namespace paravili.Steps {
	/// <summary>
	/// Datastructure of a cluster
	/// </summary>
	public class Cluster : IEnumerable<Cluster> {
		#region Public Properties

		public virtual Cluster PreviousCluster {
			get { return m_previousCluster; }
			set {
				if (value == null) {
					m_previousCluster = null;
					return;
				}
				m_previousCluster = value;
				m_previousCluster.FollowingCluster = this;
				// use id of the oldest cluster
				Id = value.Id;
				Size += value.Size + 1;
				CalculateAngle();
			}
		}

		public virtual Cluster FollowingCluster { get; private set; }

		public int Size { get; private set; }
		public Guid Id { get; private set; }
		public IList<ColoredPoint> Points { get; set; }
		public double AverageXValue { get; set; }
		public double AverageYValue { get; set; }
		public int Time { get; set; }
		public double ColorX { get; set; }
		public double ColorY { get; set; }
		public double ColorZ { get; set; }
		public double HulPerimeterSize { get; set; }
		public Point Min { get; set; }
		public Point Max { get; set; }
		public double DistanceToPreviousCluster { get; private set; }
		public double AngleToPreviousCluster { get; private set; }

		#endregion

		public Cluster() {
			Id = Guid.NewGuid();
			Size = 0;
		}

		/// <summary>
		/// create a cluster out of list of <see cref="ColoredPoint"/>
		/// </summary>
		public Cluster(IList<ColoredPoint> points, int currentTime)
			: this() {
			double xTotal = 0;
			double yTotal = 0;
			double maxX = 0;
			double minX = double.MaxValue;
			double maxY = 0;
			double minY = double.MaxValue;
			double colorX = 0;
			double colorY = 0;
			double colorZ = 0;
			int pointsCount = points.Count;
			foreach (ColoredPoint p in points) {
				int x = p.P.X;
				int y = p.P.Y;
				colorX += p.Color.X;
				colorY += p.Color.Y;
				colorZ += p.Color.Z;
				xTotal += x;
				yTotal += y;
				if (maxX < x) {
					maxX = x;
				}
				else if (minX > x) {
					minX = x;
				}
				if (maxY < y) {
					maxY = y;
				}
				else if (minY > y) {
					minY = y;
				}
			}
			Points = points;
			AverageXValue = xTotal / pointsCount;
			AverageYValue = yTotal / pointsCount;
			Time = currentTime;
			HulPerimeterSize = 2 *
							   (Math.Abs((maxX - minX) < 1 ? 1 : maxX - minX) + Math.Abs((maxY - minY) < 1 ? 1 : maxY - minY));
			Min = new Point((int)minX, (int)minY);
			Max = new Point((int)maxX, (int)maxY);
			ColorX = colorX / pointsCount;
			ColorY = colorY / pointsCount;
			ColorZ = colorZ / pointsCount;
		}

		#region IEnumerable<Cluster> Members

		public IEnumerator<Cluster> GetEnumerator() {
			return new MyEnumerator(this);
		}

		IEnumerator IEnumerable.GetEnumerator() {
			return new MyEnumerator(this);
		}

		#endregion

		#region Private Classes

		/// <summary>
		/// cluster enumerator
		/// </summary>
		private class MyEnumerator : IEnumerator<Cluster> {
			#region Public Methods

			public MyEnumerator(Cluster root) {
				m_root = root;
				Reset();
			}

			#endregion

			#region Private Fields

			private readonly Cluster m_root;
			private Cluster m_current;
			private bool m_isFirst;

			#endregion

			#region IEnumerator<Cluster> Members

			public object Current {
				get { return m_current; }
			}

			public bool MoveNext() {
				if (m_isFirst) {
					m_current = m_root;
					m_isFirst = false;
					return m_current != null;
				}
				if (m_current == null || m_current.PreviousCluster == null) {
					m_current = null;
					return false;
				}
				m_current = m_current.PreviousCluster;
				return true;
			}

			public void Reset() {
				m_isFirst = true;
			}

			Cluster IEnumerator<Cluster>.Current {
				get { return m_current; }
			}

			public void Dispose() {
			}

			#endregion
		}

		#endregion

		#region Private Methods

		private void CalculateAngle() {
			double xDistance = m_previousCluster.AverageXValue - AverageXValue;
			double yDistance = m_previousCluster.AverageYValue - AverageYValue;
			DistanceToPreviousCluster = Math.Pow(xDistance * xDistance + yDistance * yDistance, 0.5);
			double a = Math.Atan(yDistance / xDistance);
			AngleToPreviousCluster = (xDistance > 0) ? a : Math.PI - a;
		}

		#endregion

		#region Private Fields

		private Cluster m_previousCluster;

		#endregion
	}
}